-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: 执行CPLD升级
local skynet = require 'skynet'
local log = require 'mc.logging'
local vos = require 'utils.vos'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local bs = require 'mc.bitstring'
local chip = require 'unit_manager.class.logic_fw.cpld_chip'
local drivers = require 'unit_manager.class.logic_fw.upgrade.drivers_api'
local valid = require 'unit_manager.class.logic_fw.upgrade.valid'
local utils = require 'mc.utils'
local valid_handle = require 'unit_manager.class.logic_fw.upgrade.valid'

local MAX_CPLD_UPDATE_FILE_NUM = 32 -- 8家厂商，4片为例
local process = {}

local g_success_list = {}

local function get_applicable_component_fw(system_id, cfg, component_fw_list)
    local update_obj_list = {}
    if not cfg.uid then
        log:error('unsupport firmware: uid not exist')
        return update_obj_list
    end
    -- 比较所有组件的UId是否跟该升级包支持的UId匹配
    for _, fw in ipairs(component_fw_list) do
        if (system_id == defs.ALL_SYSTEM_ID or system_id == fw.system_id) and
            cfg:check_fw_uid_exist(fw) then
            log:notice('cpld fw matched, system_id = %s, fw.uid = %s, fw.csr.Name = %s',
                system_id, fw.uid, fw.csr.Name)
            table.insert(update_obj_list, fw)
        end
    end
    return update_obj_list
end

-- 加载cpld,将升级文件拷贝到cpld flash中
local function load_cpld_single(obj_fw, upg_file, file_type)
    local CPLD_UPGRADE_LOAD_MODE = 0
    local file_name_len = #upg_file
    local MAX_CPLD_LEN = 0x300000 -- 驱动限制为3M
    local file_len = vos.get_file_length(upg_file)
    if (file_len > MAX_CPLD_LEN or file_len <= 0) and file_type ~= 'svf' then
        log:error('file len:0x%x(Max:0x%x) invalid!', file_len, MAX_CPLD_LEN)
        return defs.RET.ERR
    end
    log:notice('load file[0x%x]', file_len)
    local upg_info = {
        mode = CPLD_UPGRADE_LOAD_MODE,
        product_id = 0,
        board_id = 0,
        pcb = 0x43, -- V2中用字符C赋值，这里直接用ascii码0x43表示
        component_id = 0, -- V3采用CSR配置每一种JTAG链路模式，不用再进行链路选择，cid默认填0
        file_type = file_type,
        filename_len = file_name_len,
        filename = upg_file
    }
    -- 按照V2 CPLD_UPG_FILE_S结构体小端对齐封装
    -- V2的CPLD_UPG_FILE_S结构体设计未考虑字节序对齐，mode需要修改为4字节
    local CPLD_UPG_FILE_DESC = string.format(
                                   '<<mode:4/unit:8, product_id:4/little-unit:8, ' ..
                                       'board_id:4/little-unit:8, ' ..
                                       'pcb:4/little-unit:8, component_id:4/little-unit:8, ' ..
                                       'file_type:3/string, filename_len:4/little-unit:8, filename:%d/binary>>',
                                       file_name_len)
    local info = bs.new(CPLD_UPG_FILE_DESC):pack(upg_info)
    local ok, error = pcall(obj_fw.update_chip.Write, obj_fw.update_chip,
                            require'mc.context'.new(), 0, info)
    if not ok then
        log:error('[cpld]block_write fail:%s', error)
        return defs.RET.ERR
    end
    log:notice('[cpld]%s update success!', upg_file)
    return defs.RET.OK
end

local function get_upg_valid_file_cnt(file_path, file_type)
    local valid_vme_path = nil
    local cpld_vme_path = nil
    local cnt = 0
    for i = 1, MAX_CPLD_UPDATE_FILE_NUM do
        valid_vme_path = string.format('%svalid%02d.%s', file_path, i, file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, i, file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then cnt = cnt + 1 end
    end
    log:notice('[cpld]upg file cnt = %s', cnt)
    return cnt
end

local function load_cpld_with_chip_id(logic_fw, cpld_info, upg_cfg, file_path, upgrade_list, hot_upgrade)
    local valid_vme_path = nil
    local cpld_vme_path = nil
    local file_id = 0
    local success_times = 0
    local file_num = get_upg_valid_file_cnt(file_path, upg_cfg.file_type)
    local chip_id = 0
    -- bypass偏移使能
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode,
                            logic_fw.chip_info, require'mc.context'.new(), true)
    if not ok then
        log:error('[cpld]set_bypass_mode fail:%s', error)
        return defs.RET.ERR
    end
    for _, i in pairs(upgrade_list) do
        chip_id = cpld_info[string.format('device%d_id', i)]
        file_id = chip:get_cpld_upgrade_file_id(chip_id, upg_cfg.chip_num, i)
        log:notice('[cpld]id = 0x%x, index = %s / %s, file_id = %s', chip_id, i,
                 upg_cfg.chip_num, file_id)
        -- cpld的chip序号从0开始
        drivers:set_upg_cpld_chip(logic_fw.chip_info, i - 1)
        -- cpld升级文件均为vme
        valid_vme_path = string.format('%svalid%02d.%s', file_path, file_id, upg_cfg.file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, file_id, upg_cfg.file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then
            log:notice('[cpld]update file id:%s', file_id)
            if load_cpld_single(logic_fw, cpld_vme_path, upg_cfg.file_type) == defs.RET.OK then
                -- 保存valid文件
                valid:init_valid_file(logic_fw, i, valid_vme_path, upg_cfg.file_type, hot_upgrade)
                success_times = success_times + 1
                table.insert(g_success_list, i)
            end
            skynet.sleep(20)
        end
    end
    -- 允许拆包升级，升级成功数和预期个数一样
    if success_times == #upgrade_list then
        log:notice('[cpld]upgrade cpld success.')
        return defs.RET.OK
    end
    log:error('[cpld]local cpld fail times[%s] file_num[%s] supplier_mode[%s]',
             success_times, file_num, upg_cfg.supplier_mode)
    return defs.RET.ERR
end

local function load_cpld_with_file_nums(logic_fw, cfg, file_path)
    local valid_vme_path = nil
    local cpld_vme_path = nil
    local success_times = 0
    -- bypass偏移去使能
    local ok, error = pcall(logic_fw.chip_info.SetBypassMode,
                            logic_fw.chip_info, require'mc.context'.new(), false)
    if not ok then
        log:error('[cpld]set_bypass_mode fail:%s', error)
        return defs.RET.ERR
    end
    for i = 1, MAX_CPLD_UPDATE_FILE_NUM do
        valid_vme_path = string.format('%svalid%02d.%s', file_path, i, cfg.file_type)
        cpld_vme_path = string.format('%scpld%02d.%s', file_path, i, cfg.file_type)
        if vos.get_file_accessible(valid_vme_path) and
            vos.get_file_accessible(cpld_vme_path) then
            if load_cpld_single(logic_fw, cpld_vme_path, cfg.file_type) == defs.RET.OK then
                -- 保存valid文件
                valid:init_valid_file(logic_fw, i, valid_vme_path, cfg.file_type)
                success_times = success_times + 1
                table.insert(g_success_list, i)
            end
            skynet.sleep(20)
        end
        if success_times == cfg.chip_num then return defs.RET.OK end
    end
    return defs.RET.ERR
end

local function load_cpld_multi_supplier_mode(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    local cpld_info = {}
    local ok = drivers:get_cpld_device_info(logic_fw.chip_info, cpld_info)
    if ok == defs.RET.ERR then
        log:notice('[cpld]get cpld device info failed')
        return defs.RET.ERR
    end
    -- 支持CPLD多厂商升级归一方案：通过cpld的chip信息选片升级
    if (chip:check_has_pango(cpld_info) and cfg.chip_num > 1) or
        (cfg.supplier_mode == defs.CPLD_SUPPLIER_MODE.MIXED) then
        return load_cpld_with_chip_id(logic_fw, cpld_info, cfg, file_path, upgrade_list, hot_upgrade);
    end
    -- 兼容旧的成链升级包
    return load_cpld_with_file_nums(logic_fw, cfg, file_path)
end

local function load_cpld(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    if cfg.supplier_mode <= defs.CPLD_SUPPLIER_MODE.SINGLE then
        log:notice('[cpld]_____load_cpld_single_____')
        -- 单一厂商固件包归一
        local ret = load_cpld_single(logic_fw, file_path .. 'cpld.' .. cfg.file_type, cfg.file_type)
        if ret == defs.RET.OK then
            valid:init_valid_file(logic_fw, 1, file_path .. 'valid.' .. cfg.file_type, cfg.file_type)
        end
        return ret
    else
        log:notice('[cpld]_____load_cpld_multi_supplier_mode_____')
        return load_cpld_multi_supplier_mode(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    end
end

local function load_cpld_with_lock(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
    local lock_time_out = 600
    local ok, ret_code
    while lock_time_out > 0 do
        ok, ret_code = logic_fw:update_chip_lock(require 'mc.context'.new(), 600)
        if ok and ret_code == 0 then
            local upgrade_ret = load_cpld(logic_fw, cfg, file_path, upgrade_list, hot_upgrade)
            -- 硬件总线解锁
            ok, ret_code = logic_fw:update_chip_unlock(require 'mc.context'.new())
            if not ok or ret_code ~= 0 then
                log:error("[jtag cpld upgrade] set lock status to 0 failed, ret code:%s", ret_code)
            end
            return upgrade_ret
        end
        log:debug("[jtag cpld upgrade] get chip lock failed")
        lock_time_out = lock_time_out - 1
        skynet.sleep(100)
    end

    log:error("[jtag cpld upgrade] upgrade failed because can not get chip lock")
    return defs.RET.ERR
end

local function record_logic_multi_supplier_running_log(name, upgrade_list)
    local upg_fw_name = string.gsub(name, "%d+$", "")
    for _, upg_fw in ipairs(upgrade_list) do
        for _, succ_fw in ipairs(g_success_list) do
            if upg_fw == succ_fw then
                log:running(log.RLOG_INFO, 'Upgrade %s Firmware successfully', upg_fw_name .. upg_fw)
                goto continue
            end
        end
        log:running(log.RLOG_ERROR, 'Upgrade %s Firmware failed', upg_fw_name .. upg_fw)
        ::continue::
    end
end

local function record_logic_upgrade_running_log(name, upgrade_list, one_fw_ret)
    if name and upgrade_list and #upgrade_list ~= 0 then
        return record_logic_multi_supplier_running_log(name, upgrade_list)
    end
    if one_fw_ret ~= defs.RET.OK then
        log:running(log.RLOG_ERROR, 'Upgrade %s Firmware failed', name)
        return
    end
    log:running(log.RLOG_INFO, 'Upgrade %s Firmware successfully', name)
end

local function validate_upgarde_process(fw_list, cfg_list, file_path)
    if (not fw_list) or (not cfg_list) then
        log:error('firmware or cfg not exist')
        return false
    end
    if (not file_path) or (not vos.get_file_accessible(file_path)) then
        log:error('error file_path')
        return false
    end
    return true
end

function process:upgrade_component_cpld(db, system_id, fw_list, cfg_list, file_path, upgrade_list, hot_upgrade)
    if not validate_upgarde_process(fw_list, cfg_list, file_path) then
        return defs.RET.ERR, false
    end
    -- 输入路径/dev/shm/upgrade/Firmware
    local s, e = string.find(file_path, '/.+/')
    local release_path = string.sub(file_path, s, e) -- 提取路径/dev/shm/upgrade/
    local fw_name = string.sub(file_path, e + 1, string.len(file_path)) -- 提取文件名Firmware
    local one_fw_ret = defs.RET.OK
    local final_ret = defs.RET.OK
    local is_need_valid = false
    for _, cfg in ipairs(cfg_list) do
        if cfg.name ~= fw_name then
            goto continue
        end
        -- 找到与文件名匹配cfg的节点
        -- 通过Uid找到适用的组件对象
        local component_fw_list = get_applicable_component_fw(system_id, cfg, fw_list)
        if #component_fw_list == 0 then
            log:error('no match component: firmware uid = %s', cfg.uid)
            return defs.RET.ERR, is_need_valid
        end
        -- uid适用情况下，解压在与Firmware同级文件夹
        utils.secure_tar_unzip(
            file_path,
            release_path,
            0x6400000, -- 最大解压限制100M
            1024
        )
        log:notice('[cpld]get cpld packages successful')
        for _, fw in ipairs(component_fw_list) do
            fw:switch_to_firmware_route()
            -- 启动升级
            g_success_list = {}
            one_fw_ret = load_cpld_with_lock(fw, cfg, release_path, upgrade_list, hot_upgrade)
            record_logic_upgrade_running_log(fw.name, upgrade_list, one_fw_ret)
            if one_fw_ret ~= defs.RET.OK then
                final_ret = one_fw_ret
            end
            -- 设置生效标志
            if one_fw_ret == defs.RET.OK and not hot_upgrade and not string.find(fw.csr.Name, 'FPGA') then
                valid_handle:set_validating_flag(db, fw.system_id, 1)
                is_need_valid = true
            end
            fw:switch_to_default_route()
        end
        -- 目前框架一次只发一个Firmware，使用完就可以退出了
        break
        ::continue::
    end
    return final_ret, is_need_valid
end

return process
